package com.gongj.jedis.fbsLock;

import redis.clients.jedis.params.SetParams;

import java.util.Arrays;
import java.util.UUID;

/**
 * @program: jedis
 * @description:分布式锁
 * @author: gongj
 * @Description: TODO
 * @create: 2020-07-12 13:30
 **/
public class DistributedLock {
    public static void main(String[] args) {
            //for (int i = 0; i < 10; i++) {
                //new Thread(()->{
                    DistributedLock.test4();
               // }).start();

            }








    /**
     * 发生异常无法释放锁
     * 后面来的请求全部堵塞在这里，锁也永远得不到释放。
     */
    public static void test1(){
        Redis redis = new Redis();
        redis.execute(jedsi->{
            Long aLong = jedsi.setnx("k1", "111");
            if(aLong == 1){
                //没有被占，执行自己的业务逻辑
                jedsi.set("fff","goggng");
                System.out.println(jedsi.get("fff"));
                int i = 1 /0;
                jedsi.del("k1");
            }else{
                //有人占位，停止/暂缓操作
                System.out.println("等待");
            }
        });
    }

    /**
     * 改进
     * 给锁添加一个过期时间，防止应用在运行过程中抛出异常导致锁无法及时得到释放
     * 但是还有一个问题
     * 就是在获取锁和设置过期时间之间如果如果服务器突然挂掉了，这个时
     * 候锁被占用，无法及时得到释放，也会造成死锁，因为获取锁和设置过期时间是两个操作，不具备原子
     * 性。
     */
    public static void test2(){
        Redis redis = new Redis();
        redis.execute(jedsi->{
            Long aLong = jedsi.setnx("k1", "111");
            if(aLong == 1){
                jedsi.expire("k1",10);
                //没有被占，执行自己的业务逻辑
                jedsi.set("fff","goggng");
                System.out.println(jedsi.get("fff"));
                int i = 1 /0;
                jedsi.del("k1");
            }else{
                //有人占位，停止/暂缓操作
                System.out.println("等待");
            }
        });
    }

    /**
     * 从 Redis2.8 开始，setnx 和 expire 可以通过一个命令一起来执行了
     * 还有一个问题
     * 为了防止业务代码在执行的时候抛出异常，我们给每一个锁添加了一个超时时间，超时之后，锁会被自
     * 动释放，但是这也带来了一个新的问题：如果要执行的业务非常耗时，可能会出现紊乱。举个例子：第
     * 一个线程首先获取到锁，然后开始执行业务代码，但是业务代码比较耗时，执行了 8 秒，这样，会在第
     * 一个线程的任务还未执行成功锁就会被释放了，此时第二个线程会获取到锁开始执行，在第二个线程刚
     * 执行了 3 秒，第一个线程也执行完了，此时第一个线程会释放锁，但是注意，它释放的第二个线程的
     * 锁，释放之后，第三个线程进来。
     */
    public static void test3(){
        Redis redis = new Redis();
        redis.execute(jedsi->{
            String k1 = jedsi.set("k1", "111", new SetParams().nx().ex(10));    //正常返回是ok
            if(k1 != null && "OK".equals(k1)){
                //没有被占，执行自己的业务逻辑
                jedsi.set("fff","goggng");
                System.out.println(jedsi.get("fff"));
                //int i = 1 /0;
                jedsi.del("k1");
            }else{
                //有人占位，停止/暂缓操作
                System.out.println("等待");
            }
        });
    }

    /**
     * 可以在锁上面做文章，将锁的 value 设置为一个随机字符串，每次释放锁的时候，都去比较随机
     * 字符串是否一致，如果一致，再去释放，否则，不释放。
     * 对于第二种方案，由于释放锁的时候，要去查看锁的 value，第二个比较 value 的值是否正确，第三步
     * 释放锁，有三个步骤，很明显三个步骤不具备原子性，为了解决这个问题，我们得引入 Lua 脚本。
     * Lua 脚本的优势：
     * 使用方便，Redis 中内置了对 Lua 脚本的支持。
     * Lua 脚本可以在 Redis 服务端原子的执行多个 Redis 命令。
     * 由于网络在很大程度上会影响到 Redis 性能，而使用 Lua 脚本可以让多个命令一次执行，可以有
     * 效解决网络给 Redis 带来的性能问题。
     */
    public static void test4(){
        Redis redis = new Redis();
        redis.execute(jedis->{
            //获取随机字符串
            String value = UUID.randomUUID().toString().replace("-", "");
            //获取锁
            String k1 = jedis.set("k1", value, new SetParams().nx().ex(10));    //正常返回是ok
            if(k1 != null && "OK".equals(k1)){
                //没有被占，执行自己的业务逻辑
                jedis.set("fff","goggng");
                System.out.println(jedis.get("fff"));
                //释放锁
                Object result = jedis.evalsha("8d41c65428dfb43deeff4a0cc21245c19e8908e4", Arrays.asList("k1"), Arrays.asList(value));
                              //  String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
               //Object result = jedis.eval(script, Arrays.asList("k1"), Arrays.asList(value));

               System.out.println(result);
            }else{
                //有人占位，停止/暂缓操作
                System.out.println("等待");
            }
        });
    }
}
